using System;

namespace Lichtspiel.Utility
{
    /// <summary>
    /// An object that invokes an action after an amount of time has elapsed and
    /// optionally continues repeating until told to stop.
    /// </summary>
    public sealed class Timer : IUpdateable
    {
        private bool repeats;
        private Action<Timer> tick;
        private TimeSpan tickLength;
        private TimeSpan elapsedTime;
        private bool valid;
        internal Timer() {}

        /// <summary>
        /// Creates a new Timer.
        /// </summary>
        /// <param name="length">The length of time between ticks.</param>
        /// <param name="repeats">Whether or not the timer repeats.</param>
        /// <param name="tick">The delegate to invoke when the timer ticks.</param>
        public Timer(TimeSpan length, bool repeats, Action<Timer> tick) {
            if (length <= TimeSpan.Zero) {
                throw new ArgumentException("length must be greater than 0");
            }
            if (tick == null) {
                throw new ArgumentNullException("tick");
            }

            Reset(length, repeats, tick);
        }

        /// <summary>
        /// Gets whether or not the timer is active.
        /// </summary>
        public bool IsActive {
            get { return valid; }
        }

        /// <summary>
        /// Gets whether or not this timer repeats.
        /// </summary>
        public bool Repeats {
            get { return repeats; }
        }

        /// <summary>
        /// Gets the length of time between ticks of the timer.
        /// </summary>
        public TimeSpan TickLength {
            get { return tickLength; }
        }

        #region IUpdateable Members

        /// <summary>
        /// Updates the timer.
        /// </summary>
        public void Update(GameTime time) {
            // if a timer is stopped manually, it may not
            // be valid at this point so we skip i
            if (!valid) {
                return;
            }

            // update the timer's time
            elapsedTime += time.Elapsed;

            // if the timer passed its tick length...
            if (elapsedTime >= tickLength) {
                // perform the action
                tick(this);

                // subtract the tick length in case we need to repeat
                elapsedTime -= tickLength;

                // if the timer doesn't repeat, it is no longer valid
                valid = repeats;

                if (!valid) {
                    Stop();
                }
            }
        }

        #endregion

        /// <summary>
        /// Stops the timer.
        /// </summary>
        public void Stop() {
            valid = false;
            tick = null;
        }

        /// <summary>
        /// Forces the timer to fire its tick event, invalidating the timer unless it is set to repeat.
        /// </summary>
        public void ForceTick() {
            if (!valid) {
                return;
            }

            tick(this);
            elapsedTime = TimeSpan.Zero;

            valid = Repeats;

            if (!valid) {
                Stop();
            }
        }

        internal void Reset(TimeSpan l, bool r, Action<Timer> t) {
            valid = true;
            elapsedTime = TimeSpan.Zero;
            tickLength = l;
            repeats = r;
            tick = t;
        }
    }
}